<?php
@ignore_user_abort(TRUE);

class ControllerModuleCron extends Controller {
    private $now_time;

    public function index(){
        $this->now_time=time();

        $cron_next_run_time=$this->sql_cache->get('nextrun');

        if($cron_next_run_time === FALSE){//if file do not exist, create new cron cache
            $this->nextCron();
            $cron_next_run_time=$this->sql_cache->get('nextrun');
        }

        if($cron_next_run_time <= time()){
            $this->run();
        }
    }

    private function run($cron_id = 0) {
        $this->load->model('tool/cron');

        $cron = $this->model_tool_cron->getCron($cron_id,$this->now_time);

        $process_name='cron_' . empty($cron) ? 'CRON' : $cron['cron_id'];

        if($this->process_lock->lock($process_name)){
            if($cron){
                @set_time_limit(1000);

                $cron['file_name'] = str_replace(array('..', '/', '\\'), '', $cron['file_name']);
                $cron_file = DIR_SYSTEM . 'cron/' . $cron['file_name'];

                //set next-time runtime for the current cron
                $this->setNextTime($cron);

                //cache runtime of the next cron
                $this->nextCron();

                $result = $this->model_tool_cron->executeCron($cron_file);

                if(!$result){
                    if($cron['logging']){//log cron operation
                        $this->model_tool_cron->logCron($cron, $this->now_time, TRUE);
                    }

                    $this->process_lock->unlock();

                    return FALSE;
                }
            }

            //log cron operation
            if($cron['logging']){
                $this->model_tool_cron->logCron($cron, $this->now_time);
            }

            $this->process_lock->unlock();
        }else{
            echo 'process running!';
        }
    }

    private function nextCron() {
        $this->load->model('tool/cron');

        $next_run = $this->model_tool_cron->getNextRunCron();
        if($next_run !== FALSE) {
            $this->sql_cache->set('nextrun', $next_run);
        } else {
            $this->sql_cache->set('nextrun', time() + 86400 * 365);
        }
    }

    /**
     * Generate the next run time for a particular task.
     *
     * @param array The task array as fetched from the database.
     * @return int The next run time as a UNIX timestamp
     */
    private function setNextTime($cron){
        $time = $this->now_time;
        $reset_hour=0;
        $reset_day=0;
        $reset_month=0;
        $reset_year=0;
        $next_minute = $current_minute = date("i", $time);
        $next_hour = $current_hour = date("H", $time);
        $next_day = $current_day = date("d", $time);
        $next_weekday = $current_weekday = date("w", $time);
        $next_month = $current_month = date("m", $time);
        $next_year = $current_year = date("Y", $time);

        if($cron['minute'] == "*"){
            ++$next_minute;
            if($next_minute > 59){
                $reset_hour = 1;
                $next_minute = 0;
            }
        }else{
            if($this->buildNextRunBit($cron['minute'], $current_minute) != false){
                $next_minute = $this->buildNextRunBit($cron['minute'], $current_minute);
            }else{
                $next_minute = $this->fetchFirstRunTime($cron['minute']);
            }

            if($next_minute <= $current_minute){
                $reset_hour = 1;
            }
        }

        if($reset_hour || !$this->runTimeExists($cron['hour'], $current_hour)){
            if($cron['hour'] == "*"){
                ++$next_hour;
                if($next_hour > 23){
                    $reset_day = 1;
                    $next_hour = 0;
                }
            }else{
                if($this->buildNextRunBit($cron['hour'], $current_hour) != false){
                    $next_hour = $this->buildNextRunBit($cron['hour'], $current_hour);
                }else{
                    $next_hour = $this->fetchFirstRunTime($cron['hour']);
                    $reset_day = 1;
                }

                if($next_hour < $current_hour){
                    $reset_day = 1;
                }
            }

            $next_minute = $this->fetchFirstRunTime($cron['minute']);
        }

        if($reset_day || ($cron['weekday'] == "*" && !$this->runTimeExists($cron['day'], $current_day) || $cron['day'] == "*" && !$this->runTimeExists($cron['weekday'], $current_weekday))){
            if($cron['weekday'] == "*"){
                if($cron['day'] == "*"){
                    ++$next_day;
                    if($next_day > date("t", $time)){
                        $reset_month = 1;
                        $next_day = 1;
                    }
                }else{
                    if($this->buildNextRunBit($cron['day'], $current_day) != false){
                        $next_day = $this->buildNextRunBit($cron['day'], $current_day);
                    }else{
                        $next_day = $this->fetchFirstRunTime($cron['day']);
                        $reset_month = 1;
                    }if($next_day < $current_day){
                        $reset_month = 1;
                    }
                }
            }else{
                if($this->buildNextRunBit($cron['weekday'], $current_weekday) != false){
                    $next_weekday = $this->buildNextRunBit($cron['weekday'], $current_weekday);
                }else{
                    $next_weekday = $this->fetchFirstRunTime($cron['weekday']);
                }

                $next_day = $current_day + ($next_weekday-$current_weekday);

                if($next_day <= $current_day){
                    $next_day += 7;
                }

                if($next_day > date("t", $time)){
                    $reset_month = 1;
                }
            }
            $next_minute = $this->fetchFirstRunTime($cron['minute']);
            $next_hour = $this->fetchFirstRunTime($cron['hour']);

            if($next_day == $current_day && $next_hour < $current_hour){
                $reset_month = 1;
            }
        }

        if($reset_month || !$this->runTimeExists($cron['month'], $current_month)){
            if($cron['month'] == "*"){
                $next_month++;
                if($next_month > 12){
                    $reset_year = 1;
                    $next_month = 1;
                }
            }else{
                if($this->buildNextRunBit($cron['month'], $current_month) != false){
                    $next_month = $this->buildNextRunBit($cron['month'], $current_month);
                }else{
                    $next_month = $this->fetchFirstRunTime($cron['month']);
                    $reset_year = 1;
                }

                if($next_month < $current_month){
                    $reset_year = 1;
                }
            }

            $next_minute = $this->fetchFirstRunTime($cron['minute']);
            $next_hour = $this->fetchFirstRunTime($cron['hour']);

            if($cron['weekday'] == "*"){
                $next_day = $this->fetchFirstRunTime($cron['day']);
                if($next_day == 0) $next_day = 1;
            }else{
                $next_weekday = $this->fetchFirstRunTime($cron['weekday']);
                $new_weekday = date("w", mktime($next_hour, $next_minute, 0, $next_month, 1, $next_year));
                $next_day = 1 + ($next_weekday-$new_weekday);

                if($next_weekday < $new_weekday){
                    $next_day += 7;
                }
            }

            if($next_month == $current_month && $next_day == $current_day && $next_hour < $current_hour){
                $reset_year = 1;
            }
        }

        if($reset_year){
            $next_year++;
            $next_minute = $this->fetchFirstRunTime($cron['minute']);
            $next_hour = $this->fetchFirstRunTime($cron['hour']);
            $next_month = $this->fetchFirstRunTime($cron['month']);

            if($next_month == 0) $next_month = 1;

            if($cron['weekday'] == "*"){
                $next_day = $this->fetchFirstRunTime($cron['day']);
                if($next_day == 0) $next_day = 1;
            }else{
                $next_weekday = $this->fetchFirstRunTime($cron['weekday']);
                $new_weekday = date("w", mktime($next_hour, $next_minute, 0, $next_month, 1, $next_year));
                $next_day = 1 + ($next_weekday-$new_weekday);

                if($next_weekday < $new_weekday){
                    $next_day += 7;
                }
            }
        }

        $next_run = mktime($next_hour, $next_minute, 0, $next_month, $next_day, $next_year);

        $this->load->model('tool/cron');
        $this->model_tool_cron->setNextRunTime($cron['cron_id'], $next_run, $this->now_time);
    }

    /**
     * Builds the next run time bit for a particular item (day, hour, month etc). Used by setNextTime().
     *
     * @param string A string containing the run timse for this particular item
     * @param int The current value (be it current day etc)
     * @return int The new or found value
     */
    private function buildNextRunBit($data, $bit){
        if($data == "*") return $bit;
        $data = explode(",", $data);
        
        foreach($data as $thing){
            if($thing > $bit){
                return $thing;
            }
        }
        
        return false;
    }

    /**
     * Fetches the fist run bit for a particular item (day, hour, month etc). Used by setNextTime().
     *
     * @param string A string containing the run times for this particular item
     * @return int The first run time
     */
    private function fetchFirstRunTime($data){
        if($data == "*") return "0";
        $data = explode(",", $data);
        return $data[0];
    }

    /**
     * Checks if a specific run time exists for a particular item (day, hour, month etc). Used by setNextTime().
     *
     * @param string A string containing the run times for this particular item
     * @param int The bit we're checking for
     * @return boolean True if it exists, false if it does not
     */
    private function runTimeExists($data, $bit){
        if($data == "*") return true;
        $data = explode(",", $data);

        if(in_array($bit, $data)){
            return true;
        }

        return false;
    }
}
?>